跳到主要内容

MyBatis 编写 Mapper

概述

参考资料 XML约束——DTD约束

使用了 MyBatis 之后就无需再像传统的方式那样去写 JDBC 实现类了,而是使用 ORM(对象关系映射)将 SQL 语句的结果封装成一个对象

直接通过 XML 的形式去添加一个 Mapper 文件

注意:一般这个 Mapper 映射文件的包和实体的包(Dao 层)是在一起的,所以在 resources 目录下创建一个同名的包,例如 com/alsritter/mapper 不要用 . 使用的是 / ,否则会把 . 当成字符名称创建一个包名

配置环境

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>

<!-- MySQL的驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>

<!-- 单元项目 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>

<!-- 日志系统 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

编写 Mapper 的基本流程

创建一个 Dao 层的接口

定义一个 UserMapper 接口

public interface UserMapper {
List<User> getUserList();
}

编写对应的 Mapper 文件

小知识:这里的 XML 使用的是 DTD约束

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace去绑定一个对应的Dao/Mapper接口 -->
<mapper namespace="com.alsritter.dao.UserMapper">
<!-- 绑定方法,以及返回类的坐标(resultType) -->
<select id="getUserList" resultType="com.alsritter.pojo.User">
select * from users
</select>
</mapper>

namespace:namespace中的包名要和 Dao/Mapper 接口中的包名一致

id:就是对应的namespace的方法名

resultType:就是SQL语句执行的返回值,增删改使用 int 当作返回值(修改条目)

添加映射

在核心配置文件 (mybatis-config.xml) 里添加上这个映射

<mappers>
<mapper resource="com\alsritter\dao\UserMapper.xml"/>
</mappers>

使用上面写的 Mapper 查询数据

// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("UserMapper.xml");

// 获取 sqlSession 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 获取 sqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();

// 执行 sql 语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();

代理开发的方式

以前写好 Dao 层接口之后还需要手动再创建一个实现类去实现那个接口,但是现在使用 MyBatis 之后可以使用其动态代理对象来动态创建一个实现类(现在开发的主流)

如下,使用 Mapper 的 getMapper 方法获取一个实现类(动态代理模式)

// 执行 sql 语句
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();

使用例就看上面的 “编写 Mapper 的基本流程”

不使用接口的方式

上面编写接口的步骤其实可以省略,完全只使用 Mapper 文件也可以(其实如果使用接口文件则是偏向注解开发的),但是一般都不使用这种无接口的方式,因为写接口的目的就是为了分离出 Dao 层,如果直接通过下面这种指定的方式写会造成代码耦合度很高

下面演示一下不用接口完全使用 XML 文件的方式编写一个查询方法

一样,先写个 Mapper 文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 这里随便写一个不存在的命名空间 -->
<mapper namespace="userMapper">
<!-- 绑定方法,以及返回类的坐标(resultType) -->
<select id="getUserList" resultType="com.alsritter.pojo.User">
select * from users
</select>

<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select * from users where id = #{id};
</select>

<insert id="addUser" parameterType="com.alsritter.pojo.User">
insert into studyjdbc.users (name ,pwd) values (#{name},#{pwd});
</insert>

<update id="updateUser" parameterType="com.alsritter.pojo.User">
update studyjdbc.users set name = #{name},pwd = #{pwd} where id = #{id};
</update>

<delete id="deleteUser" parameterType="int">
delete from studyjdbc.users where id = #{id};
</delete>
</mapper>

使用上面写的 Mapper 查询数据

// 加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream("UserMapper.xml");

// 获取 sqlSession 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 获取 sqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();

// 直接通过在 XML 文件写的命名空间和 id 来读取数据
// 1、查询数据
List<User> userList = sqlSession.selectList("userMapper.getUserList");
// 2、添加数据
sqlSession.insert("userMapper.addUser", user);
// 3、修改数据
sqlSession.update("userMapper.updateUser", user);
// 4、删除数据
sqlSession.delete("userMapper.deleteUser", id);

基本的 CRUD 实例

构建数据元

@Data@ToString@AllArgsConstructor@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}

编写 Mapper 接口

public interface UserMapper {
List<User> getUserList();
//根据id查询用户
User getUserById(int id);
//insert一个用户,注意,增删改需要提交事务
int addUser(User user);
//update一个用户
int updateUser(User newUser);
//delete一个用户
int deleteUser(int id);
}

编写 Mapper 映射

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace去绑定一个对应的Dao/Mapper接口 -->
<mapper namespace="com.alsritter.dao.UserMapper">
<select id="getUserList" resultType="com.alsritter.pojo.User">
select * from users;
</select>

<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select * from users where id = #{id};
</select>

<insert id="addUser" parameterType="com.alsritter.pojo.User">
insert into studyjdbc.users (name ,pwd) values (#{name},#{pwd});
</insert>

<update id="updateUser" parameterType="com.alsritter.pojo.User">
update studyjdbc.users set name = #{name},pwd = #{pwd} where id = #{id};
</update>

<delete id="deleteUser" parameterType="int">
delete from studyjdbc.users where id = #{id};
</delete>
</mapper>

编写测试类

public class UserMapperTest {
@Test
public void test() {
// 第一步:获取SqlSession对象
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();

for (User user : userList) {
System.out.println(user);
}
}
}

@Test
public void getUserById() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.getUserById(1));
}
}

@Test
public void addUser() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.addUser(new User(6,"小爱","2131270")));

// 如果要执行需要更新的操作,一般需要手动提交事务
sqlSession.commit();
}
}

@Test
public void updateUser(){
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.updateUser(new User(6,"Lisa","1313270")));
sqlSession.commit();
}
}
@Test
public void deleteUser(){
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.deleteUser(1));
sqlSession.commit();
}
}
}

插入数据类型

通过标签的 parameterType 属性来指定要插入的数据类型

<select id="getUserById" parameterType="int" resultType="com.alsritter.pojo.User">
select * from users where id = #{id};
</select>
<insert id="addUser" parameterType="com.alsritter.pojo.User">
insert into studyjdbc.users (name ,pwd) values (#{name},#{pwd});
</insert>

插入的数据也必须和上面的参数一致

@Test
public void getUserById() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.getUserById(1));
}
}

@Test
public void addUser() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.addUser(new User(6,"小爱","2131270")));
// 如果要执行需要更新的操作,一般需要手动提交事务
sqlSession.commit();
}
}

传递参数给 SQL 语句

有两种方式传参 #{name}${name}

使用${}方式传入的参数,mybatis不会对它进行特殊处理,而使用#{}传进来的参数,mybatis默认会将其当成字符串。

#{name}:预编译的形式,可以防止sql注入 ${name}:不带预编译,直接通过字符拼接的形式

使用 Map 来填入参数

前面使用 User 当作参数传进去相对而言没有那么自由 因为必须使用 POJO 里相同的参数名来传 而使用 Map 之后,可以直接使用 Key 名来取值

<insert id="addUser02" parameterType="map">
insert into studyjdbc.users (name ,pwd) values (#{userName},#{password});
</insert>
// 使用Map来填入参数
int addUser02(Map<String,Object> map);
@Test
public void addUser02(){
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("userName","小王");
map.put("password","1214214145");
System.out.println(mapper.addUser02(map));
sqlSession.commit();
}
}

模糊查询

但是注意,这样写可能会存在 SQL注入 的问题

//模糊查询
List<User> getUserByLikeList(String value);
<select id="getUserByLikeList" parameterType="String" resultType="com.alsritter.pojo.User">
select * from users where name like #{value};
</select>
@Test
public void getUserByLikeList() {
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// 执行SQl
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserByLikeList("李%");
for (User user : userList) {
System.out.println(user);
}
}
}

提交事务

前面的操作后面都加了个 sqlSession.commit(); 手动提交 MyBatis 默认是关闭自动提交的,如果要打开则把 前面写好的工具类 openSession 传递一个Boolean变量 其函数原型为

SqlSession openSession(boolean autoCommit);

所以传入一个true就可以打开自动提交了

public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}

不过,最好还是手动提交,防止一些错误信息也给提交上去了

分页查询

SQL里的limit关键字

-- 例
-- 从0开始分页 2个为一页(注意,前闭后开)
SELECT * FROM user limit 0,2;

传统使用SQL的方式

一般使用这种方式就行了

<select id="getUsersByLimit" parameterType="map" resultType="com.alsritter.pojo.User">
select * from users limit #{startIndex} , #{pageSize};
</select>

接口

List<User> getUsersByLimit(Map<String,Integer> map);

测试

@Test
public void test(){
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<>();
map.put("startIndex",0);
map.put("pageSize",3);
List<User> usersByLimit = mapper.getUsersByLimit(map);
usersByLimit.forEach((i)->{
logger.info(i);
});
}
}

RowBounds 来分页

不推荐这种方式

<select id="getUserByBounds" resultType="com.alsritter.pojo.User">
select * from users;
</select>

接口

// 使用RowBounds来分页
List<User> getUserByBounds();
@Test
public void test(){
try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
// UserMapper mapper = sqlSession.getMapper(UserMapper.class); 直接通过会话来调用方法
List<User> usersByLimit =sqlSession.selectList("com.alsritter.dao.UserMapper.getUserByBounds"null,new RowBounds(0,2));
usersByLimit.forEach((i)->{
logger.info(i);
});
}
}

使用注解开发

注意!!使用 class 读取不要在路径下同时使用注解包和xml,不然会报重复映射的错误

常用注解

常用的注解 注解名 | 作用 ----|--- @Select | 选择语句 @Insert | 插入 @Update | 更新 @Delete | 删除 @Param | 传递参数 @Result | 实现结果集封装 参考上面那个 resultMap 高级结果集映射 @Results | 封装多个结果集 (配合 @Result 使用) @One | 实现一对一结果集封装 @Many | 实现一对多结果集封装

使用例

直接在接口上加上注解

public interface UserMapper {
@Select("select * from users;")
List<User> getUserList();
}

然后把原本配置文件上的Mapper映射xml改成映射类

原:

<mappers>
<mapper resource="com\alsritter\dao\UserMapper.xml"/>
</mappers>

新:

<mappers>
<mapper class="com.alsritter.dao.UserMapper"/>
</mappers>

下面同理

传递参数

使用 @Param 注解

@Select("select * from users where id = #{id};")
User getUserById(@Param("id") int id);

@Param("id") 里的参数名与sql语句里的一致,形参名可以不同

注意:这个 @Param("name") 是可以使用引用类型 . 出属性的

如下所示:

@Insert("insert into users(name, pwd) VALUE(#{user.name},#{user.pwd})")
int addUser(@Param("user")User user);

结果集映射

注解@Results@Result来结果集映射

<mapper namespace="data.UserMapper">
<resultMap type="data.User" id="userResultMap">
<!-- 用id属性来映射主键字段 -->
<id property="id" column="user_id"/>
<!-- 用result属性来映射非主键字段 -->
<result property="userName" column="user_name"/>
</resultMap>
</mapper>

使用注解的形式

@Select("select * from t_user where user_name = #{userName}")
@Results(
@Result(property = "userId", column = "user_id"),
@Result(property = "userName", column = "user_name")
)
User getUserByName(@Param("userName") String userName);